/*
 * @(#)LittleEndianOutputStream.java  1.0.1 99/05/19
 * 
 * Copyright 1998, 1999 Elliotte Rusty Harold
 *  
 */

package com.macfaq.io;

import java.io.DataOutput;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UTFDataFormatException;

/**
 * A little endian output stream writes primitive Java numbers 
 * and characters to an output stream in a little endian format. 
 * The standard java.io.DataOutputStream class which this class
 * imitates uses big-endian integers.
 * Modified 8/11/2016 by Steven Phillips to advertise that it implements DataOutput.
 *
 * @author  Elliotte Rusty Harold
 * @version 1.0.1, 19 May 1999
 * @see     java.io.DataOutputStream
 */
public class LittleEndianOutputStream extends FilterOutputStream implements DataOutput {

  /**
   * The number of bytes written so far to the little endian output stream. 
   */
  protected int written;

  /**
   * Creates a new little endian output stream and chains it to the  
   * output stream specified by the out argument. 
   *
   * @param   out   the underlying output stream.
   * @see     java.io.FilterOutputStream#out
   */
  public LittleEndianOutputStream(OutputStream out) {
    super(out);
  }

  /**
   * Writes the specified byte value to the underlying output stream. 
   *
   * @param      b   the <code>byte</code> value to be written.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public synchronized void write(int b) throws IOException {
    out.write(b);
    written++;
  }

  /**
   * Writes <code>length</code> bytes from the specified byte array 
   * starting at <code>offset</code> to the underlying output stream.
   *
   * @param      data     the data.
   * @param      offset   the start offset in the data.
   * @param      length   the number of bytes to write.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public synchronized void write(byte[] data, int offset, int length) 
   throws IOException {
    out.write(data, offset, length);
    written += length;
  }


  /**
   * Writes a <code>boolean</code> to the underlying output stream as 
   * a single byte. If the argument is true, the byte value 1 is written.
   * If the argument is false, the byte value <code>0</code> in written.
   *
   * @param      b   the <code>boolean</code> value to be written.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public void writeBoolean(boolean b) throws IOException {
  
    if (b) this.write(1);
    else this.write(0);

  }

  /**
   * Writes out a <code>byte</code> to the underlying output stream
   *
   * @param      b   the <code>byte</code> value to be written.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public void writeByte(int b) throws IOException {
    out.write(b);
    written++;
  }

  /**
   * Writes a two byte <code>short</code> to the underlying output stream in
   * little endian order, low byte first. 
   *
   * @param      s   the <code>short</code> to be written.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public void writeShort(int s) throws IOException {

    out.write(s & 0xFF);
    out.write((s >>> 8) & 0xFF);
    written += 2;
    
  }

  /**
   * Writes a two byte <code>char</code> to the underlying output stream 
   * in little endian order, low byte first. 
   *
   * @param      c   the <code>char</code> value to be written.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public void writeChar(int c) throws IOException {

    out.write(c & 0xFF);
    out.write((c >>> 8) & 0xFF);
    written += 2;
    
  }

  /**
   * Writes a four-byte <code>int</code> to the underlying output stream 
   * in little endian order, low byte first, high byte last
   *
   * @param      i   the <code>int</code> to be written.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public void writeInt(int i) throws IOException {

    out.write(i & 0xFF);
    out.write((i >>> 8) & 0xFF);
    out.write((i >>> 16) & 0xFF);
    out.write((i >>> 24) & 0xFF);
    written += 4;
    
  }

  /**
   * Writes an eight-byte <code>long</code> to the underlying output stream 
   * in little endian order, low byte first, high byte last
   *
   * @param      l   the <code>long</code> to be written.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public void writeLong(long l) throws IOException {

    out.write((int) l & 0xFF);
    out.write((int) (l >>> 8) & 0xFF);
    out.write((int) (l >>> 16) & 0xFF);
    out.write((int) (l >>> 24) & 0xFF);
    out.write((int) (l >>> 32) & 0xFF);
    out.write((int) (l >>> 40) & 0xFF);
    out.write((int) (l >>> 48) & 0xFF);
    out.write((int) (l >>> 56) & 0xFF);
    written += 8;

  }

 /**
  * Writes a 4 byte Java float to the underlying output stream in
  * little endian order.
  *
  * @param      f   the <code>float</code> value to be written.
  * @exception  IOException  if an I/O error occurs.
  */
  public final void writeFloat(float f) throws IOException {
  
    this.writeInt(Float.floatToIntBits(f));
    
  }

 /**
  * Writes an 8 byte Java double to the underlying output stream in
  * little endian order.
  *
  * @param      d   the <code>double</code> value to be written.
  * @exception  IOException  if an I/O error occurs.
  */
  public final void writeDouble(double d) throws IOException {
  
    this.writeLong(Double.doubleToLongBits(d));
    
  }

  /**
   * Writes a string to the underlying output stream as a sequence of 
   * bytes. Each character is written to the data output stream as 
   * if by the <code>writeByte()</code> method. 
   *
   * @param      s   the <code>String</code> value to be written.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public void writeBytes(String s) throws IOException {

    int length = s.length();
    for (int i = 0; i < length; i++) {
      out.write((byte) s.charAt(i));
    }
    written += length;
    }

  /**
   * Writes a string to the underlying output stream as a sequence of 
   * characters. Each character is written to the data output stream as 
   * if by the <code>writeChar</code> method. 
   *
   * @param      s   a <code>String</code> value to be written.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public void writeChars(String s) throws IOException {

    int length = s.length();
    for (int i = 0; i < length; i++) {
      int c = s.charAt(i);
      out.write(c & 0xFF);
      out.write((c >>> 8) & 0xFF);
    }
    written += length * 2;
    
  }

  /**
   * Writes a string of no more than 65,535 characters 
   * to the underlying output stream using UTF-8 
   * encoding. This method first writes a two byte short 
   * in <b>big</b> endian order as required by the 
   * UTF-8 specification. This gives the number of bytes in the 
   * UTF-8 encoded version of the string, not the number of characters
   * in the string. Next each character of the string is written
   * using the UTF-8 encoding for the character.
   *
   * @param      s   the string to be written.
   * @exception  UTFDataFormatException if the string is longer than 
   *             65,535 characters.
   * @exception  IOException  if the underlying stream throws an IOException.
   */
  public void writeUTF(String s) throws IOException {

    int numchars = s.length();
    int numbytes = 0;

    for (int i = 0 ; i < numchars ; i++) {
      int c = s.charAt(i);
      if ((c >= 0x0001) && (c <= 0x007F)) numbytes++;
      else if (c > 0x07FF) numbytes += 3;
      else numbytes += 2;
    }

    if (numbytes > 65535) throw new UTFDataFormatException();     

    out.write((numbytes >>> 8) & 0xFF);
    out.write(numbytes & 0xFF);
    for (int i = 0 ; i < numchars ; i++) {
      int c = s.charAt(i);
      if ((c >= 0x0001) && (c <= 0x007F)) {
        out.write(c);
      }
      else if (c > 0x07FF) {
        out.write(0xE0 | ((c >> 12) & 0x0F));
        out.write(0x80 | ((c >>  6) & 0x3F));
        out.write(0x80 | (c & 0x3F));
        written += 2;
      } 
      else {
        out.write(0xC0 | ((c >>  6) & 0x1F));
        out.write(0x80 | (c & 0x3F));
        written += 1;
      }
    }
    
    written += numchars + 2;
    
  }

  /**
   * Returns the number of bytes written to this little endian output stream.
   * (This class is not thread-safe with respect to this method. It is 
   * possible that this number is temporarily less than the actual 
   * number of bytes written.)
   * @return  the value of the <code>written</code> field.
   */
  public int size() {
    return this.written;
  }
  
}
